package hawk.example.bookstore.repository.impl;

import static glz.hawkframework.support.ArgumentSupport.*;

import glz.hawkframework.dao.base.process.InsertProcessor;
import glz.hawkframework.dao.base.process.UpdateProcessor;
import glz.hawkframework.sql.condition.Condition;
import glz.hawkframework.sql.util.QueryWrapper;

import hawk.example.bookstore.mapper.BookMapper;
import hawk.example.bookstore.po.BookPo;
import hawk.example.bookstore.repository.BookRepository;
import hawk.example.bookstore.sql.provider.BookSqlProvider;
import hawk.example.bookstore.update.BookColumnUpdate;
import hawk.example.bookstore.update.BookUpdate;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import javax.annotation.Nullable;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class BookRepositoryImpl implements BookRepository {
    private final BookMapper bookMapper;
    private final BookSqlProvider bookSqlProvider;
    @Autowired(required = false)
    private InsertProcessor insertProcessor;
    @Autowired(required = false)
    private UpdateProcessor updateProcessor;

    public BookRepositoryImpl(BookMapper bookMapper, BookSqlProvider bookSqlProvider) {
        this.bookMapper = argNotNull(bookMapper, "bookMapper");
        this.bookSqlProvider = argNotNull(bookSqlProvider, "bookSqlProvider");
    }

    @Override
    public void insert(BookPo bookPo) {
        argNotNull(bookPo, "bookPo");
        if (insertProcessor != null) {
            insertProcessor.beforeInsert(bookPo);
        }
        if (bookMapper.insert(bookSqlProvider.insert(bookPo)) != 1) {
            throw new IllegalStateException("Failed to insert bookPo.");
        }
        if (insertProcessor != null) {
            insertProcessor.afterInsert(bookPo);
        }
    }

    @Override
    public void insertSelective(BookPo bookPo) {
        argNotNull(bookPo, "bookPo");
        if (insertProcessor != null) {
            insertProcessor.beforeInsert(bookPo);
        }
        if (bookMapper.insertSelective(bookSqlProvider.insertSelective(bookPo)) != 1) {
            throw new IllegalStateException("Failed to insert bookPo.");
        }
        if (insertProcessor != null) {
            insertProcessor.afterInsert(bookPo);
        }
    }

    @Override
    public void insertMultiple(List<BookPo> bookPos) {
        final int defaultChunkSize = 200;
        insertMultiple(bookPos, defaultChunkSize);
    }

    @Override
    public void insertMultiple(List<BookPo> bookPos, int chunkSize) {
        argNotEmpty(bookPos, "bookPos");
        argument(chunkSize, c -> c > 0, c -> "The parameter['chunkSize'] must be greater than 0.");
        for (int i = 0; i < bookPos.size(); i += chunkSize) {
            List<BookPo> chunk = bookPos.subList(i, Math.min(i + chunkSize, bookPos.size()));
            if (insertProcessor != null) {
                insertProcessor.beforeInsert(chunk);
            }
            if (bookMapper.insertMultiple(bookSqlProvider.insertMultiple(chunk)) != chunk.size()) {
                throw new IllegalStateException("Failed to insert bookPos.");
            }
            if (insertProcessor != null) {
                insertProcessor.afterInsert(chunk);
            }
        }
    }

    @Override
    public void deleteByPrimaryKey(BigInteger bookId, Long recordVersion) {
        argNotNull(bookId, "bookId");
        argNotNull(recordVersion, "recordVersion");
        if (bookMapper.deleteGeneral(bookSqlProvider.deleteByPrimaryKey(bookId, recordVersion)) != 1) {
            throw new IllegalStateException("Failed to delete bookPo.");
        }
    }

    @Override
    public void updateByPrimaryKey(BookUpdate bookUpdate, BigInteger bookId, Long recordVersion) {
        argNotNull(bookUpdate, "bookUpdate");
        argNotNull(bookId, "bookId");
        argNotNull(recordVersion, "recordVersion");
        HashMap<String, Object> params = null;
        if (updateProcessor != null) {
            params = new HashMap<>();
            params.put(UpdateProcessor.UPDATE_BY_FIELD_OBJECT, bookUpdate);
            params.put(UpdateProcessor.RECORD_VERSION, recordVersion);
            params.put(UpdateProcessor.RECORD_ID, bookId);
            updateProcessor.beforeUpdate(params);
        }
        if (bookMapper.update(bookSqlProvider.updateByPrimaryKey(bookUpdate, bookId, recordVersion)) != 1) {
            throw new IllegalStateException("Failed to update bookPo.");
        }
        if (updateProcessor != null) {
            updateProcessor.afterUpdate(params);
        }
    }

    @Override
    public Optional<BookPo> getByPrimaryKey(BigInteger bookId) {
        argNotNull(bookId, "bookId");
        return bookMapper.selectOne(bookSqlProvider.selectByPrimaryKey(bookId));
    }

    /**
     * Throws an exception if found no required record by the primary key.
     */
    @Override
    public BookPo loadByPrimaryKey(BigInteger bookId) {
        argNotNull(bookId, "bookId");
        return bookMapper.selectOne(bookSqlProvider.selectByPrimaryKey(bookId)).orElseThrow(RuntimeException::new);
    }

    /**
     * Throws the supplied exception if found no required record by the primary key.
     */
    @Override
    public <E extends Throwable> BookPo loadByPrimaryKey(BigInteger bookId, Supplier<? extends E> exceptionSupplier) throws E {
        argNotNull(bookId, "bookId");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        return bookMapper.selectOne(bookSqlProvider.selectByPrimaryKey(bookId)).orElseThrow(exceptionSupplier);
    }

    @Override
    public boolean existByPrimaryKey(BigInteger bookId) {
        argNotNull(bookId, "bookId");
        return bookMapper.count(bookSqlProvider.countByPrimaryKey(bookId)) == 1;
    }

    /**
     * Throws an exception if the required record doesn't exist
     */
    @Override
    public void assertExistByPrimaryKey(BigInteger bookId) {
        argNotNull(bookId, "bookId");
        if (bookMapper.count(bookSqlProvider.countByPrimaryKey(bookId)) != 1) {
            throw new RuntimeException("Record not exist");
        }
    }

    /**
     * Throws the supplied exception if the required record doesn't exist
     */
    @Override
    public <E extends Throwable> void assertExistByPrimaryKey(BigInteger bookId, Supplier<? extends E> exceptionSupplier) throws E {
        argNotNull(bookId, "bookId");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        if (bookMapper.count(bookSqlProvider.countByPrimaryKey(bookId)) != 1) {
            throw exceptionSupplier.get();
        }
    }

    @Override
    public Optional<BookPo> queryOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        return bookMapper.selectOne(bookSqlProvider.selectOrCountDynamic(queryWrapper));
    }

    @Override
    public List<BookPo> queryMany(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        return bookMapper.selectMany(bookSqlProvider.selectOrCountDynamic(queryWrapper));
    }

    /**
     * Throws an exception if found no required record.
     */
    @Override
    public BookPo loadOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        return bookMapper.selectOne(bookSqlProvider.selectOrCountDynamic(queryWrapper)).orElseThrow(RuntimeException::new);
    }

    /**
     * Throws the supplied exception if found no required record.
     */
    @Override
    public <E extends Throwable> BookPo loadOne(QueryWrapper queryWrapper, Supplier<? extends E> exceptionSupplier) throws E {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        return bookMapper.selectOne(bookSqlProvider.selectOrCountDynamic(queryWrapper)).orElseThrow(exceptionSupplier);
    }

    @Override
    public long count(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        return bookMapper.count(bookSqlProvider.selectOrCountDynamic(queryWrapper));
    }

    @Override
    public boolean exist(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        return bookMapper.count(bookSqlProvider.selectOrCountDynamic(queryWrapper)) > 0;
    }

    /**
     * Throws an exception if found no required record.
     */
    @Override
    public void assertExist(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        if (bookMapper.count(bookSqlProvider.selectOrCountDynamic(queryWrapper)) <= 0) {
            throw new RuntimeException();
        }
    }

    /**
     * Throws the supplied exception if found no required record.
     */
    @Override
    public <E extends Throwable> void assertExist(QueryWrapper queryWrapper, Supplier<? extends E> exceptionSupplier) throws E {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        if (bookMapper.count(bookSqlProvider.selectOrCountDynamic(queryWrapper)) <= 0) {
            throw exceptionSupplier.get();
        }
    }

    @Override
    public boolean existOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        return bookMapper.count(bookSqlProvider.selectOrCountDynamic(queryWrapper)) == 1;
    }

    /**
     * Throws an exception if found no required record or found more than one record.
     */
    @Override
    public void assertExistOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        if (bookMapper.count(bookSqlProvider.selectOrCountDynamic(queryWrapper)) == 0) {
            throw new RuntimeException();
        }
        if (bookMapper.count(bookSqlProvider.selectOrCountDynamic(queryWrapper)) > 1) {
            throw new RuntimeException();
        }
    }

    /**
     * Throws the supplied exception if found no required record or found more than one record.
     */
    @Override
    public <E extends Throwable> void assertExistOne(QueryWrapper queryWrapper, Supplier<? extends E> exceptionSupplier) throws E {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        if (bookMapper.count(bookSqlProvider.selectOrCountDynamic(queryWrapper)) == 0) {
            throw exceptionSupplier.get();
        }
        if (bookMapper.count(bookSqlProvider.selectOrCountDynamic(queryWrapper)) > 1) {
            throw exceptionSupplier.get();
        }
    }

    @Override
    public long delete(@Nullable Condition condition) {
        return bookMapper.deleteGeneral(bookSqlProvider.deleteDynamic(condition));
    }

    @Override
    public long update(BookColumnUpdate bookColumnUpdate, @Nullable Condition condition) {
        argNotNull(bookColumnUpdate,"bookColumnUpdate");
        HashMap<String, Object> params = null;
        if (updateProcessor != null) {
            params = new HashMap<>();
            params.put(UpdateProcessor.UPDATE_BY_COLUMN_OBJECT, bookColumnUpdate);
            updateProcessor.beforeUpdate(params);
        }
        long affectedRowCount =  bookMapper.updateGeneral(bookSqlProvider.updateDynamic(bookColumnUpdate, condition));
        if (updateProcessor != null) {
            updateProcessor.afterUpdate(params);
        }
        return affectedRowCount;
    }
}
